﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Reflective
{
    /// <summary>
    /// This class implements a wrapper around the non-generic runtime <see cref="DynamicMethod"/>,
    /// to make it easier to define dynamic methods using a delegate as a template.
    /// </summary>
    /// <typeparam name="TDelegate">
    /// The delegate type to build a dynamic method for.
    /// </typeparam>
    public sealed class DynamicMethod<TDelegate>
        where TDelegate : class
    {
        private readonly DynamicMethod _Method;
        private readonly ILGenerator _IL;

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMethod&lt;TDelegate&gt;"/> class.
        /// </summary>
        public DynamicMethod()
            : this(string.Empty, false)
        {
            // Do nothing here
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMethod&lt;TDelegate&gt;"/> class.
        /// </summary>
        /// <param name="name">
        /// The name of the dynamic method. This can be a zero-length string, but it cannot be <c>null</c>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="name"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TDelegate"/> is not a delegate type.</para>
        /// </exception>
        public DynamicMethod(string name)
            : this(name, false)
        {
            // Do nothing here
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMethod&lt;TDelegate&gt;"/> class.
        /// </summary>
        /// <param name="name">
        /// The name of the dynamic method. This can be a zero-length string, but it cannot be <c>null</c>.
        /// </param>
        /// <param name="restrictedSkipVisibility">
        /// <c>true</c> to skip JIT visibility checks on types and members accessed by the MSIL of the dynamic method,
        /// with this restriction: the trust level of the assemblies that contain those types and members must be
        /// equal to or less than the trust level of the call stack that emits the dynamic method; otherwise, <c>false</c>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="name"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TDelegate"/> is not a delegate type.</para>
        /// </exception>
        public DynamicMethod(string name, bool restrictedSkipVisibility)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            Type delegateType = typeof(TDelegate);
            if (!typeof(Delegate).IsAssignableFrom(delegateType))
                throw new ArgumentException("The type specified is not a delegate");

            MethodInfo invokeMethod = delegateType.GetMethod("Invoke");

            ParameterInfo[] parameters = invokeMethod.GetParameters();
            Type[] parameterTypes =
                (from parameter in parameters
                 select parameter.ParameterType).ToArray();

            _Method = new DynamicMethod(name, invokeMethod.ReturnType, parameterTypes, restrictedSkipVisibility);

            for (int index = 0; index < parameterTypes.Length; index++)
                _Method.DefineParameter(index + 1, parameters[index].Attributes, parameters[index].Name);

            _IL = _Method.GetILGenerator();
        }

        /// <summary>
        /// Gets the underlying <see cref="DynamicMethod"/> object.
        /// </summary>
        public DynamicMethod Method
        {
            get
            {
                return _Method;
            }
        }

        /// <summary>
        /// Gets the <see cref="ILGenerator"/> associated with the <see cref="Method"/>.
        /// </summary>
        public ILGenerator IL
        {
            get
            {
                return _IL;
            }
        }

        /// <summary>
        /// Completes the dynamic method and creates a delegate that can be used to execute it.
        /// </summary>
        /// <returns>
        /// A delegate of the specified <typeparamref name="TDelegate"/> type, which can be used to execute the dynamic method.
        /// </returns>
        public TDelegate CreateDelegate()
        {
            return _Method.CreateDelegate<TDelegate>();
        }

        /// <summary>
        /// Completes the dynamic method and creates a delegate that can be used to execute it, specifying an object the delegate is bound to.
        /// </summary>
        /// <param name="target">
        /// An object the delegate is bound to. Must be of the same type as the first parameter of the dynamic method. 
        /// </param>
        /// <returns>
        /// A delegate of the specified <typeparamref name="TDelegate"/> type, which can be used to execute the dynamic method.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="target"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><paramref name="target"/> is not the same type as the first parameter of the dynamic method, and is not assignable to that type.</para>
        /// </exception>
        public TDelegate CreateDelegate(object target)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            return _Method.CreateDelegate<TDelegate>(target);
        }
    }
}